Skip to content

Admin Users: persist requested storage, rename and regroup filters, add clear action - #6096

Draft
akolson wants to merge 8 commits into
learningequality:unstablefrom
akolson:add-requested-storage-value
Draft

Admin Users: persist requested storage, rename and regroup filters, add clear action#6096
akolson wants to merge 8 commits into
learningequality:unstablefrom
akolson:add-requested-storage-value

Conversation

@akolson

@akolson akolson commented Aug 18, 2026

Copy link
Copy Markdown
Member

Summary

This fixes QA follow-ups from #5946, all on the admin Users page.

  • Requested storage never reached the "Storage needed" column. information["space_needed"] is only written at registration, and only when the user ticks "Storing materials for private or local use" — the post-registration storage request flow just sent an email and persisted nothing. So users who requested storage after signing up showed blank, while older users who answered the registration question showed a value. StorageSettingsView now records the requested amount on the user, and the CSV prefers it over the registration answer. No migration: information is an existing JSONField.
  • "Has Studio edits" was misnamed. The filter is Exists(Change.objects.filter(created_by=...)), which matches any Change row the user ever created — creating a channel qualifies, and deleting it later doesn't undo it. That is activity, not edits, which is what QA observed. Renamed in the filter row and the CSV header; the query param and backend annotation are unchanged, so existing bookmarked URLs still work.
  • The two checkboxes sat in separate quarter-width columns, leaving the wide gap in QA's screenshot. They now share one half-width flex row with a "Clear filters" action at the end.
  • Clear filters removes every filter while preserving pagination and sorting. It appears only once a filter differs from the default its control already displays — so choosing "All" for user type shows it (the select is blank until you pick something), while "Any time" on the date windows, or ticking and unticking a checkbox, does not.
  • Migrated the UserTable.spec to Vue Testing Library.
  • Internationalized the UserTable component.
  • Migrated the UserTable component to KDS. VDataTable, CountryField, and UserItem still wrap Vuetify and are deliberately left unchanged, as they require more involved refactoring.
  • Streamlined the UserTable component to the Composition API.

On sign-up (storage specified)
Screenshot 2026-08-19 at 12 39 47

On request storage (storage requested)
Screenshot 2026-08-19 at 12 44 01

UI updates
Before
image

After
image

References

Fixes #5946

Reviewer guidance

  1. Register and activate a new user.
  2. Generate a Users export from the Users table.
  3. Verify that the storage size specified during registration matches the value in the report.
  4. Log in as the new user and request additional storage.
  5. Generate another Users export from the Users table.
  6. Verify that the requested storage is reflected in the report.
  7. Test the Users table filters and verify that they continue to work as expected.

AI usage

Claude Code (Opus 5) diagnosed the reported bugs and proposed fixes and a refactoring plan. The code and approach were reviewed and verified by @akolson.

akolson and others added 3 commits August 19, 2026 13:29
A storage request only ever sent an email, so the admin Users CSV
"Storage needed" column could only show what a user typed at
registration. Users who requested storage afterwards showed blank.

Persist the requested amount to user.information and prefer it over the
registration answer when building the CSV row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Has Studio edits" matched any Change row the user ever created, so it
reported activity rather than edits; renamed to "Has Studio activity" in
the filter row and the CSV header.

On wide viewports the two checkboxes sat in separate quarter-width
columns, leaving a large gap between them. They now share one half-width
flex row alongside a "Clear filters" action, which resets every filter
while preserving pagination and sorting. The action is enabled only once
a filter differs from the default its control already displays.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The spec drove the component through wrapper.vm — assigning filters and
reading computed properties — so it asserted internals rather than
behaviour. Rewritten against Vue Testing Library, per the frontend
testing guidelines, querying by label and role and asserting on the
dispatched fetch payload.

Two problems surfaced once the queries went through the accessibility
tree. "Clear filters" used appearance="basic-link", which KButton
renders as an anchor; disabled is not a valid attribute there and
KButton's click handler does not guard on it, so the greyed-out link was
still focusable and still fired. It is now shown only when there is
something to clear. Studio's IconButton passes ariaLabel="text" unbound,
leaving icon buttons with no usable accessible name, so those two are
still reached by their existing data-test hooks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@akolson
akolson force-pushed the add-requested-storage-value branch from 3715d35 to 456055a Compare August 19, 2026 10:31
@akolson
akolson changed the base branch from hotfixes to unstable August 19, 2026 10:31
akolson and others added 5 commits August 20, 2026 16:54
Every user-facing string on the page was hardcoded English, including the
browser tab title, the filter option labels, the table headers and the CSV
snackbars. Moved them into a usersStrings translator module, each with a
context note for translators, and replaced the count strings' ternaries with
ICU plurals.

The spec references the message keys rather than the English text, per the
frontend testing guidelines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace the Vuetify layout and form controls above the users table with
their Kolibri Design System equivalents: VLayout/VFlex become KGrid and
KGridItem, VSelect becomes KSelect, VTextField becomes KTextbox with a
prepended KIcon, and VProgressLinear becomes KLinearLoader.

KSelect emits `select` only on user action, so the filters bind `:value`
and `@select` rather than v-model, which would otherwise write the
defaults back into the URL and undo "Clear filters".

VDataTable, CountryField and UserItem still wrap Vuetify and are left for
a later pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The component mixed an Options API shell with composables. Move it wholly
into `<script setup>`: the Vuex getters become computeds over useStore,
routerMixin's tab title becomes an onMounted call to updateTabTitle, and
the responsive headers become a plain function of the breakpoint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four comments restated what the code already said: two doc blocks naming
their own helpers, and a pointer to renderWithFilters repeated above two
calls to it. The remaining comments each cite behaviour of code outside
this file that a test has to work around.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CountryField gains fullWidth, which switches its own max-width from 500px
to 100%. KTextbox caps the inner UiTextbox at max-width: 400px, which a
pass-through class cannot reach, so it is overridden through the
component's appearanceOverrides prop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@akolson
akolson force-pushed the add-requested-storage-value branch from b07ec95 to 7cde310 Compare August 20, 2026 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hotfixes - Users - QA notes for "Add Kolibri-usage filters and CSV export to admin Users page"

1 participant